# --*-- coding: utf-8 --*--
"""测试常用的基础工具类"""
import json
import os
import random
import shutil
import ssl
import string
import chardet
import openpyxl
import openpyxl.styles
import pandas as pd
import psutil
from typing import Union, NoReturn
from faker import Faker
from openpyxl import load_workbook
from openpyxl.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet

ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

load_wait_short = 2
load_wait_middle = 4
load_wait_long = 6


class CommonTools(object):
    """测试人员常用工具类接口"""

    def __init__(self):
        pass

    # staticmethod用于修饰类中的方法,使其可以在不创建类实例的情况下调用方法，这样做的好处是执行效率比较高。
    @staticmethod
    def queryJsonDict(objIterable, key, **kwargs):
        """
        从响应中根据key获取值(所有)
        :param objIterable: 传入存储字典的列表，或者字典。如响应结果:json。
        :param key: 目标key
        :param **kwargs: 增加筛选条件。字典中满足kwarg传入的‘查询条件key’=‘value’时，才取‘目标key’对应的值，否则忽略此字典的结果
        :return: 查到的值(list)
        """
        ret = []

        # if len(kwargs):
        #     print(f"----------->过滤查询条件：{kwargs}")
        def _get_value_from_json_dict(objIterable, _key, **kwargs):
            if isinstance(objIterable, list):
                for _i in objIterable:
                    _get_value_from_json_dict(_i, _key, **kwargs)
            elif isinstance(objIterable, dict):
                flag = True
                for kwargsK, kwargsV in kwargs.items():
                    if objIterable.get(kwargsK) != kwargsV:
                        flag = False
                for _k, _v in objIterable.items():
                    if _k == _key:
                        if flag:
                            ret.append(_v)
                    else:
                        _get_value_from_json_dict(_v, _key, **kwargs)
            else:
                return

        _get_value_from_json_dict(objIterable, key, **kwargs)

        return ret

    @staticmethod
    def queryoneJsonDict(dict1, values, **kwargs):
        """
        从响应中根据key获取对应的值(取到第一个即返回)
        :param dict1: 响应结果json
        :param values: key
        :param **kwargs: 增加筛选条件，字典中满足kwarg传入的‘查询条件key’=‘value’时，才取‘目标key’对应的值，否则忽略
        :return: 查到的值(str)
        """
        value = None
        values1 = values

        def GET_LIST(list1, values1):
            for i in list1:

                if isinstance(i, dict):
                    global value
                    value = TestUtils().queryoneJsonDict(i, values1, **kwargs)
                elif isinstance(i, list):
                    value = GET_LIST(i, values1)
                else:
                    continue
                if value:
                    return value

        for k, v in dict1.items():
            if value:
                break
            flag = True
            for kwargsK, kwargsV in kwargs.items():
                if dict1.get(kwargsK) != kwargsV:
                    flag = False
            if k == values:
                if flag:
                    value = v
                    return value
            elif isinstance(v, dict):
                value = TestUtils().queryoneJsonDict(v, values1, **kwargs)
            elif isinstance(v, list):  # 判断类型是不是list
                value = GET_LIST(v, values1)
            else:
                continue
            if value:
                return value
        if not value:
            value = ""
        return str(value)

    @staticmethod
    def readPageDataExcel(file_path, sheet_name: Union[str, int] = 0, idx=None):
        """
        读取excel数据
        :param file_path: excel路径  type:str
        :param sheet_name: sheet页名称  type:int or str
        :param idx: 索引  type: int
        :return: [dict1, dict2]
        """
        try:
            df = pd.read_excel(file_path, sheet_name=sheet_name, engine="openpyxl")
        except FileNotFoundError as file_not_found_error:
            raise FileNotFoundError(
                f"{file_path}，该文件不存在，{file_not_found_error}")
        except ValueError as value_error:
            raise FileNotFoundError(
                f"{file_path}，该文件不存在 {sheet_name} sheet页，{value_error}")
        if not idx:
            # for i in df.index:
            #     a = df.columns[0:3:1])].fillna("").to_dict()
            return tuple(df.loc[i, list(df.columns[0::1])].fillna(
                "").to_dict() for i in df.index[::])
        elif idx > 1:
            return dict(df.loc[idx - 2].fillna(""))
        else:
            raise KeyError("--> idx最小值为2，请确认是否输入错误")

    @staticmethod
    def readExcel(path: str, column: int = 1, row: int = 1, sheet: int = 1) -> str:
        """
        读取Excel某一单元格数据
        :param path: Excel路径 type=str
        :param sheet: 第几个sheet页或sheet页名称 type=int or str
        :param row: 行数 type=int
        :param column: 列数或列字段名 type=int or str
        :return: 单元格的值
        """
        if not isinstance(row, int):
            raise TypeError(f"----------> row参数类型应为int类型而不是“{type(row)}”")
        wb = openpyxl.load_workbook(path)
        if isinstance(sheet, str):
            ws = wb[sheet]
            column = __class__._returnCloumn(ws=ws, p_column=column)
            return ws[column + str(row)].value
        elif isinstance(sheet, int):
            ws = wb[wb.sheetnames[sheet - 1]]
            column = __class__._returnCloumn(ws=ws, p_column=column)
            return ws[column + str(row)].value
        else:
            raise TypeError(
                f"----------> sheet参数类型应为int或str类型而不是“{type(sheet)}”，数字表示为第几个sheet页，字符表示sheet页名称")

    def _returnCloumn(self, ws: Worksheet, p_column: Union[str, int]) -> str:
        """
        :param ws: sheet页 type=openpyxl.worksheet.worksheet.Worksheet
        :param p_column: 列名或列数 type=int or str
        :return: 列数转换为字母形式
        """
        if isinstance(p_column, int):
            return openpyxl.utils.get_column_letter(p_column)  # 数字转换为字母
        elif isinstance(p_column, str):
            row_list = list(map(lambda x: x.value, list(ws[1])))  # 解析第一行
            if row_list.count(p_column) == 1:  # 相同字段名存在的个数
                for index, cell in enumerate(row_list):
                    if cell == p_column:
                        return openpyxl.utils.get_column_letter(index + 1)
            elif row_list.count(p_column) == 0:
                raise ValueError(
                    f"----------> 当前sheet页“{ws.title}”不存在字段名为“{p_column}”")
            else:
                raise ValueError(
                    f"----------> 当前sheet页“{ws.title}”存在多个字段名为“{p_column}”")
        else:
            raise TypeError(
                f"----------> column参数类型应为int或str类型而不是“{type(p_column)}”，int表示第几列，str表示第一行列字段名")

    @staticmethod
    def write_excel(path: str, value: str = "", column: int = 1, row: int = 1, sheet: int = 1) -> NoReturn:
        """
        写入Excel指定单元格数据
        :param path: Excel路径 type=str
        :param sheet: 第几个sheet页或sheet页名称 type=int or str
        :param row: 行数 type=int
        :param column: 列数或列字段名 type=int or str
        :param value: 需要写入的数据 type=str
        """
        if not isinstance(row, int):
            raise TypeError(f"----------> row参数类型应为int类型而不是“{type(row)}”")
        if not isinstance(value, str):
            raise TypeError(f"----------> value参数类型应为str类型而不是“{type(value)}”")
        wb = openpyxl.load_workbook(path)
        if isinstance(sheet, str):
            ws = wb[sheet]
        elif isinstance(sheet, int):
            ws = wb[wb.sheetnames[sheet - 1]]
        else:
            raise TypeError(
                f"----------> sheet参数类型应为int或str类型而不是“{type(sheet)}”，数字表示为第几个sheet页，字符表示sheet页名称")
        column = __class__._returnCloumn(ws=ws, p_column=column)
        ws[column + str(row)].value = value
        wb.save(path)

    @staticmethod
    def getFileCoding(filepath: str) -> dict:
        """
        说明：获取文件的编码格式
        :param filepath: 文件路径 type=str
        :return: {'confidence': 1.0, 'encoding': 'UTF-8'} type=dict
        """
        with open(file=filepath, mode="rb") as f:
            s = f.read()
        return chardet.detect(s)

    @staticmethod
    def randomPhoneNum(locale: Union[str, list] = "zh_CN") -> str:
        """
        说明:生成中国大陆手机号
        :return: str
        """
        return Faker(locale=locale).phone_number()

    @staticmethod
    def randomIdCard(locale: Union[str, list] = "zh_CN") -> str:
        """
        说明:
                生成中国大陆身份证号
        :return: str
        """
        return Faker(locale=locale).ssn()

    @staticmethod
    def randomEmail(locale: Union[str, list] = "zh_CN") -> str:
        """
        说明:
                生成邮箱
        :return: str
        """
        return Faker(locale=locale).email()

    @staticmethod
    def randomIpAddress(locale: Union[str, list] = "zh_CN", ip_type: str = "ipv4") -> str:
        """
        说明:
                生成IP地址
        :return: str
        """
        if ip_type == "ipv4":
            return Faker(locale=locale).ipv4()
        elif ip_type == "ipv6":
            return Faker(locale=locale).ipv6()
        else:
            raise ValueError(f"----------> 不支持 {ip_type} ip类型")

    @staticmethod
    def randomName(locale: Union[str, list] = "zh_CN"):
        """
        生成随机姓名
        :param locale:
        :return:
        """
        return Faker(locale=locale).name()

    @staticmethod
    def makeArchive(base_name: str, for_mat: str = "zip", root_dir=None) -> str:
        """
        说明：压缩指定文件夹/目录
        :param base_name: 压缩后文件名/路径
        :param for_mat: 压缩格式 "zip", "tar", "bztar", "gztar"
        :param root_dir: 被压缩目录
        :return: 压缩后文件路径
        """
        return shutil.make_archive(
            base_name=base_name,
            format=for_mat,
            root_dir=root_dir
        )

    @staticmethod
    def unpackArchive(filename: str, extract_dir=None, for_mat=None) -> NoReturn:
        """
        说明：解压指定文件
        :param for_mat: 解压格式，如果未指定则取压缩文件的后缀名格式
        :param filename: 待解压文件名/路径
        :param extract_dir: 解压到指定目录
        """
        shutil.unpack_archive(filename=filename, extract_dir=extract_dir, format=for_mat)

    @staticmethod
    def randomCharacter(char_type="int", length="4"):
        """
        根据字符类型和长度生成随机字符
        :param char_type: 字符类型(int: 数字；str：大小写英文字母；all：数字+字母+符号)，默认值为int
        :param length: 所需生成的字符长度，默认值为4
        :return:
        """
        try:
            length = int(length)
        except ValueError as e:
            raise ValueError("----------> 长度(length字段)只能输入数字，请重新输入")
        if char_type.lower() == "int":
            s = string.digits
        elif char_type.lower() == "str":
            s = string.ascii_letters
        elif char_type.lower() == "all":
            s = string.digits + string.ascii_letters + string.punctuation
        else:
            raise ValueError("----------> 当前只支持 int、str、all 三种选项，请重新选择")
        random.shuffle(list(s))
        return "".join(random.choice(s) for _ in range(length))

    @staticmethod
    def randomZhCharacter(length="50", char_type="simple"):
        """
        根据长度生成中文字符
        \n:param length: 所需生成的字符长度，默认值为50
        :return:
        """
        try:
            length = int(length)
        except ValueError as e:
            raise ValueError("----------> 长度(length字段)只能输入数字，请重新输入")
        if char_type == "simple":
            str_ret = ""
            for _ in range(length):
                try:
                    str_ret += bytes.fromhex(
                        f"{random.randint(0xb0, 0xd6): x}{random.randint(0xa1, 0xfe): x}").decode("gb2312")
                except UnicodeDecodeError:
                    str_ret += "中"
            return str_ret
        else:
            return "".join(chr(random.randint(0x4E00, 0x9FBF))
                           for _ in range(length))

    @staticmethod
    def saveExcel(excelData: list, excelName='测试excel.xlsx'):
        """
        保存成excel文件
        :param excelData: 二维列表，excel的数据
        :param excelName: excel.xlsx文件的路径加文件名
        :return: 生成excel文件，表头默认上色
        """
        # 创建工作簿
        workbook = Workbook()
        sheet = workbook.active
        try:
            if not isinstance(excelData[0], list):
                print("请传入二维列表")
                raise Exception
        except Exception as err:
            print(err)
            return err
        # 填充数据
        for row in excelData:
            sheet.append(row)
        fill = openpyxl.styles.PatternFill('solid', fgColor='b7dee8')
        # 表头上色
        for i in range(1, len(excelData[0])):
            sheet.cell(row=1, column=i).fill = fill
        # 保存工作簿
        workbook.save(excelName)

    @staticmethod
    def readExcelData(src_file, hang=1, lie=1000):
        """
        读取excel.xlsx第几行开始、共多少列的数据，传入列数超出实际最长数据，则按实际返回。
        :param src_file:Excel路径
        :param lie: 一共多少列，具体列数，从1开始算
        :param hang: 需要第几行开始的内容，从1开始算
        :return: 二维列表
        """
        if hang < 1 or lie < 1:
            return [[]]
        # 读取excel
        wb = load_workbook(filename=src_file)
        sheetNameList = wb.sheetnames
        my_sheet = wb[sheetNameList[0]]
        list_value_all = []
        for row in my_sheet.rows:
            list_value_one = []
            for cell in row:
                list_value_one.append(cell.value)
            if len(list_value_one) > lie:
                list_value_one = list_value_one[:lie]
            list_value_all.append(list_value_one)
        if len(list_value_all) > hang:
            list_value_all = list_value_all[hang - 1:]
        return list_value_all

    @staticmethod
    def sortedMatrix(DoubleList, index=0):
        """
        二维列表根据某个字段排序
        :param index: 按子列表的此下标字段排序
        :param DoubleList: 需要被排序的二维列表
        :return:
        """
        return sorted(DoubleList, key=(lambda x: x[index]))

    @staticmethod
    def readJson(JsonPath):
        with open(JsonPath, "r", encoding="utf-8") as fr:
            return json.loads(fr.read())

    @staticmethod
    def killAppProcesses(appName='chromedriver.exe'):
        """杀掉多余的应用进程"""
        try:
            os.system(f"taskkill /f /im {appName} /t  >/dev/null 2>&1")
        except:
            pass

    @staticmethod
    def kill_process_by_name(process_name):
        """根据进程名杀死进程"""
        try:
            for proc in psutil.process_iter():
                if proc.name() == process_name:
                    proc.kill()
        except:
            pass

    @staticmethod
    def kill_other_processes(process_name=None):
        """
        杀死除了当前进程的所有名叫process_name的进程
        """
        # 获取当前进程的 PID
        current_pid = os.getpid()
        if not process_name:
            process_name = psutil.Process(current_pid).name  # 没传值则取当前进程名称
        # 获取所有运行中的进程
        processes = psutil.process_iter(['pid', 'name'])
        # 找到名为“黑色八点.exe”的进程
        matching_processes = [process for process in processes if process.name() == process_name]
        # 保留当前进程，杀死其他进程
        for process in matching_processes:
            if process.pid != current_pid:
                process.kill()
